/*
******************************************************************************
*
*   Copyright (C) 1997-2006, International Business Machines
*   Corporation and others.  All Rights Reserved.
*
******************************************************************************
*
* File CMUTEX.C
*
* Modification History:
*
*   Date        Name        Description
*   04/02/97    aliu        Creation.
*   04/07/99    srl         updated
*   05/13/99    stephen     Changed to umutex (from cmutex).
*   11/22/99    aliu        Make non-global mutex autoinitialize [j151]
******************************************************************************
*/

#include "inc/utypes.h"
#include "ucln_cmn.h"
#include "inc/uclean.h"
#include "uassert.h"

#include "local.h"
#include <_pubcrt.h>

/*
 * A note on ICU Mutex Initialization and ICU startup:
 *
 *   ICU mutexes, as used through the rest of the ICU code, are self-initializing.
 *   To make this work, ICU uses the _ICU GLobal Mutex_ to synchronize the lazy init
 *   of other ICU mutexes.  For the global mutex itself, we need some other mechanism
 *   to safely initialize it on first use.  This becomes important if two or more
 *   threads were more or less simultaenously the first to use ICU in a process, and
 *   were racing into the mutex initialization code.
 *
 *   The solution for the global mutex init is platform dependent.
 *   On POSIX systems, C-style init can be used on a mutex, with the
 *   macro PTHREAD_MUTEX_INITIALIZER.  The mutex is then ready for use, without
 *   first calling pthread_mutex_init().
 *
 *   Windows has no equivalent statically initialized mutex or CRITICAL SECION.
 *   InitializeCriticalSection() must be called.  If the global mutex does not
 *   appear to be initialized, a thread will create and initialize a new
 *   CRITICAL_SECTION, then use a Windows InterlockedCompareAndExchange to
 *   avoid problems with race conditions.
 *
 *   If an application has overridden the ICU mutex implementation
 *   by calling u_setMutexFunctions(), the user supplied init function must
 *   be safe in the event that multiple threads concurrently attempt to init
 *   the same mutex.  The first thread should do the init, and the others should
 *   have no effect.
 *
 */

#define  MAX_MUTEXES  2
static UMTX              gGlobalMutex          = NULL;
#if (ICU_USE_THREADS == 1)
static UBool             gMutexPoolInitialized = FALSE;
static char              gMutexesInUse[MAX_MUTEXES];

static UMTX gMutexes[MAX_MUTEXES] = {NULL, NULL};
#endif /* ICU_USE_THREADS==1 */

/*
 *   umtx_lock
 */
U_CAPI void  U_EXPORT2
umtx_lock(UMTX *mutex)
{
	if (mutex == NULL) {
        mutex = &gGlobalMutex;
    }

    if (*mutex == NULL) {
        /* Lock of an uninitialized mutex.  Initialize it before proceeding.   */
        umtx_init(mutex);
    }

#if (ICU_USE_THREADS == 1)
	CrtEnterLock(*mutex);
#endif /* ICU_USE_THREADS==1 */
}

/*
 * umtx_unlock
 */
U_CAPI void  U_EXPORT2
umtx_unlock(UMTX *mutex)
{
    if(mutex == NULL) {
        mutex = &gGlobalMutex;
    }

    if(*mutex == NULL)    {
#if (ICU_USE_THREADS == 1)
        U_ASSERT(FALSE);  /* This mutex is not initialized.     */
#endif
        return;
    }

#if (ICU_USE_THREADS==1)
	CrtLeaveLock(*mutex);
#endif /* ICU_USE_THREADS == 1 */
}



/*
 *   initGlobalMutex    Do the platform specific initialization of the ICU global mutex.
 *                      Separated out from the other mutexes because it is different:
 *                      Mutex storage is static for POSIX, init must be thread safe
 *                      without the use of another mutex.
 */
static void initGlobalMutex() {
#if (ICU_USE_THREADS == 1)
    if (gMutexPoolInitialized == FALSE) {
        int i;
        for (i=0; i<MAX_MUTEXES; i++) {
            CrtInitializeLock(&gMutexes[i]);
        }
        gMutexPoolInitialized = TRUE;
    }

    /*
     * for both Windows & POSIX, the first mutex in the array is used
     *   for the ICU global mutex.
     */
    gGlobalMutex = gMutexes[0];
    gMutexesInUse[0] = 1;

#else  /* ICU_USE_THREADS */
    gGlobalMutex = &gGlobalMutex;  /* With no threads, we must still set the mutex to
                                    * some non-null value to make the rest of the
                                    *   (not ifdefed) mutex code think that it is initialized.
                                    */
#endif /* ICU_USE_THREADS */
}

U_CAPI void  U_EXPORT2
umtx_init(UMTX *mutex)
{
	int i;

    if (mutex == NULL || mutex == &gGlobalMutex) {
        initGlobalMutex();
    } else {
        umtx_lock(NULL);
        if (*mutex != NULL) {
            /* Another thread initialized this mutex first. */
            umtx_unlock(NULL);
            return;
        }

#if (ICU_USE_THREADS == 1)
            /*  Search through our pool of pre-allocated mutexes for one that is not
            *  already in use.    */
            for (i=0; i<MAX_MUTEXES; i++) {
                if (gMutexesInUse[i] == 0) {
                    gMutexesInUse[i] = 1;
                    *mutex = gMutexes[i];
                    break;
                }
            }
#endif
        umtx_unlock(NULL);

#if (ICU_USE_THREADS == 1)
        /* No more mutexes were available from our pre-allocated pool.  */
        /*   TODO:  how best to deal with this?                    */
        U_ASSERT(*mutex != NULL);
#endif
    }
}

/*
 *  umtx_destroy.    Un-initialize a mutex, releasing any underlying resources
*                   that it may be holding.  Destroying an already destroyed
 *                   mutex has no effect.  Unlike umtx_init(), this function
 *                   is not thread safe;  two threads must not concurrently try to
 *                   destroy the same mutex.
 */
U_CAPI void  U_EXPORT2
umtx_destroy(UMTX *mutex) {
	int i;

    if (mutex == NULL) {  /* destroy the global mutex */
        mutex = &gGlobalMutex;
    }

    if (*mutex == NULL) {  /* someone already did it. */
        return;
    }

#if (ICU_USE_THREADS == 1)
        /* Return this mutex to the pool of available mutexes, if it came from the
         *  pool in the first place.
         */
        /* TODO use pointer math here, instead of iterating! */
        for (i=0; i<MAX_MUTEXES; i++)  {
            if (*mutex == gMutexes[i]) {
                gMutexesInUse[i] = 0;
                break;
            }
        }
#endif

    *mutex = NULL;
}


/*
 *  Mutex Cleanup Function
 *
 *      Destroy the global mutex(es), and reset the mutex function callback pointers.
 */
U_CFUNC UBool umtx_cleanup(void) {
    umtx_destroy(NULL);

#if (ICU_USE_THREADS == 1)
    if (gMutexPoolInitialized) {
        int i;
        for (i=0; i<MAX_MUTEXES; i++) {
            CrtDeleteLock(&gMutexes[i]);
            gMutexesInUse[i] = 0;
        }
    }
    gMutexPoolInitialized = FALSE;
#endif

    return TRUE;
}
